package com.liaoyifan.core.util;

import com.liaoyifan.core.opera.RequestLocaleResolver;
import com.liaoyifan.core.valid.Lang;
import jakarta.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.slf4j.MDC;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.util.ContentCachingRequestWrapper;

@SuppressWarnings("unused")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestContext {

    public static final String X_TRACE = "X-Trace";

    public static final String BEARER = "Bearer ";

    public static final String AUTHORIZATION = "Authorization";

    private static final List<String> KEY_HEADERS =
            List.of(
                    "host", // 目标服务地址（标识请求指向的域名/端口）
                    "user-agent", // 客户端标识（浏览器/设备信息）
                    "cookie", // 用户会话信息
                    "accept-language", // 客户端语言偏好（用于国际化展示）
                    "origin", // 跨域请求的源地址（协议+域名+端口，跨域鉴权）
                    "referer", // 请求来源页面（用于追踪来源或防CSRF）
                    "authorization", // 接口鉴权信息（Token、Basic Auth等）
                    "content-type", // 请求体格式（如application/json）
                    "x-requested-with", // 异步请求标识（如XMLHttpRequest，区分AJAX/普通请求）
                    "x-forwarded-for", // 代理服务器添加的客户端 IP 链
                    "x-real-ip", // 代理服务器直接传递的客户端真实 IP（比x-forwarded-for更简洁）
                    "x-trace" // 链路追踪标识
                    );

    public static final LocaleResolver LOCALE_RESOLVER = new RequestLocaleResolver();

    private static final ThreadLocal<Supplier<String>> AUTHORIZATION_HOLDER = new ThreadLocal<>();

    public static final Set<String> NO_BODY_METHODS =
            Set.of(
                    RequestMethod.GET.name(),
                    RequestMethod.HEAD.name(),
                    RequestMethod.OPTIONS.name(),
                    RequestMethod.TRACE.name());

    public static final int MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB

    public static HttpServletRequest getRequest() {
        ServletRequestAttributes request =
                ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
        if (request == null) {
            throw new RuntimeException("No HTTP request context available");
        }
        return request.getRequest();
    }

    public static String getAuthorization() {
        Supplier<String> result = AUTHORIZATION_HOLDER.get();
        return result == null ? "" : result.get();
    }

    public static void init(HttpServletRequest request) {
        LocaleContextHolder.setLocale(LOCALE_RESOLVER.resolveLocale(request));
        AUTHORIZATION_HOLDER.set(
                () -> {
                    String authorization = Token.getAuthorization(request);
                    return authorization == null ? "" : authorization;
                });
        String header = request.getHeader(X_TRACE);
        if (header == null || header.isBlank()) {
            MDC.put(X_TRACE, UID.get());
        } else {
            MDC.put(X_TRACE, header);
        }
    }

    public static Runnable init(Runnable runnable) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        Locale locale = LocaleContextHolder.getLocale();
        Supplier<String> authorization = AUTHORIZATION_HOLDER.get();
        String trace = MDC.get(X_TRACE);
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                LocaleContextHolder.setLocale(locale);
                AUTHORIZATION_HOLDER.set(authorization);
                MDC.put(X_TRACE, trace);
                runnable.run();
            } finally {
                RequestContext.clear();
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }

    public static void clear() {
        LocaleContextHolder.resetLocaleContext();
        AUTHORIZATION_HOLDER.remove();
        MDC.clear();
    }

    public static String getTrace() {
        return MDC.get(X_TRACE);
    }

    public static boolean isMultipartRequest(HttpServletRequest request) {
        return Lang.startsWithIgnoreCase(request.getContentType(), "multipart/");
    }

    public static boolean isJsonRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        return contentType != null
                && MediaType.APPLICATION_JSON.isCompatibleWith(
                        MediaType.parseMediaType(contentType));
    }

    public static String getRequestBody(HttpServletRequest request) {
        if (request instanceof ContentCachingRequestWrapper contentCachingRequestWrapper) {
            return contentCachingRequestWrapper.getContentAsString();
        }
        return null;
    }

    public static String getFormData(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        if (request instanceof MultipartHttpServletRequest multipartHttpServletRequest) {
            Map<String, Object> args = new HashMap<>();
            args.putAll(getRequestParams(multipartHttpServletRequest));
            args.putAll(getFileParams(multipartHttpServletRequest));
            return Json.toString(args);
        }
        return Json.toString(request.getParameterMap());
    }

    private static Map<String, List<Map<String, String>>> getFileParams(
            MultipartHttpServletRequest request) {
        Map<String, List<Map<String, String>>> files = new HashMap<>();
        request.getMultiFileMap()
                .forEach(
                        (k, v) ->
                                files.put(
                                        k,
                                        v.stream()
                                                .map(
                                                        file -> {
                                                            Map<String, String> map =
                                                                    new HashMap<>();
                                                            map.put(
                                                                    "name",
                                                                    file.getOriginalFilename());
                                                            map.put(
                                                                    "size",
                                                                    String.valueOf(file.getSize()));
                                                            map.put("type", file.getContentType());
                                                            return map;
                                                        })
                                                .collect(Collectors.toList())));
        return files;
    }

    public static Map<String, String> getRequestHeaders(HttpServletRequest request) {
        Map<String, String> headers = new LinkedHashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        return headers;
    }

    public static Map<String, String> getKeyHeaders(HttpServletRequest request) {
        Map<String, String> headers = new LinkedHashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (KEY_HEADERS.contains(headerName.toLowerCase())) {
                headers.put(headerName, request.getHeader(headerName));
            }
        }
        return headers;
    }

    public static Map<String, Object> getRequestParams(HttpServletRequest request) {
        Map<String, Object> params = new LinkedHashMap<>();
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            params.put(paramName, request.getParameter(paramName));
        }
        return params;
    }

    public static String getMethodArgs(Object[] args) {
        if (args == null || args.length == 0) return null;
        if (args.length == 1) return Json.toString(args[0]);
        return Json.toString(args);
    }
}
